"""Manages loading plugin objects."""

import os
import sys
import imp
import fnmatch
from types import *
from libskysh.cmd import cmd_t
from libskysh.macro import macro_t


class file_loader_t(object):
    """Works like dir_loader_t, except it operates on a single FILE_.
    That single file may contain multiple command and macro definitions.

    You can query the "command" and "macro" lists, which contain classes
    definitions can be instantiated.
    """

    def __init__(self, file_):
        self.macros = []
        self.commands = []
        module = self.load_file(file_)
        self.parse(module)

    def load_file(self, file_):
        d, f = os.path.split(file_)
        n, _0 = os.path.splitext(f)
        (fp, pathname, desc) = imp.find_module(n, [d])
        try:
            module = imp.load_module(n, fp, pathname, desc)
        except:
            fp.close() # Civic duty.
            raise      # Percolate upwards.
        return module

    def parse(self, module):
        attrs = filter(lambda a: not fnmatch.fnmatch(a, "_*"), dir(module))        
        for a in attrs:
            o = getattr(module, a)
            if self._is_class(o):
                if issubclass(o, cmd_t):
                    self.commands.append(o)
                elif issubclass(o, macro_t):
                    self.macros.append(o)

    def _is_class(self, o):
        return type(o) in [ClassType] or isinstance(o, object)
        

class dir_loader_t(object):
    """Walks the supplied DIR_, and populates two properties:

        commands
        macros

    which are lists of tuples of (cmd_t, path) objects and (macro_t, path)
    objects. The directory is recursively searched.
    """
    
    def __init__(self, dir_):
        self.commands = []
        self.macros = []
        os.path.walk(dir_, self.walk, None)

    def walk(self, _0, dname, fnames):
        f = fnmatch.filter(fnames, "*.py")
        f += fnmatch.filter(fnames, "*.pyc")
        self.walk_inner(dname, fnames)

    def walk_inner(self, dname, fnames):
        for f in fnames:
            p = os.path.join(dname, f)
            loader = file_loader_t(p)
            self.commands += map(lambda c, p=p: (c, p), loader.commands)
            self.macros += map(lambda m, p=p: (m, p), loader.macros)
            
    
def load(dir_):
    """Returns a tuple of (commands, macros) objects. These objects have not
    been instantiatied."""
    dl = dir_loader_t(dir_)
    return dl.commands, dl.macros
